import java.util.*;
import java.io.*;

public class Controller extends Thread {

   private static final int	sleepPeriod  = 800;
   private static final int	idlePeriod   = 250;
   private static final int	stateNEW	 = 0;
   private static final int	stateREADY   = 1;
   private static final int	stateRUNNING = 2;
   private static final int	statePAUSED  = 3;
   private static final int	statePENDING = 4;
   private static final int	stateSTOPPED = 5;

   private static final int 	slaveDISABLED 	 = 0;
   private static final int 	slaveDOWN	 	 = 1;
   private static final int 	slaveLOADED  	 = 2;
   private static final int 	slaveTARGETED	 = 3;
   private static final int   slaveUP		 = 4;
   private static final int   slaveBAD		 = 5;

   private static final int   runNOT		 = 0;
   private static final int   runRUNNING		 = 1;
   private static final int   runDONE 		 = 2;
   private static final int   runREPORTED		 = 3;

   private static final int   sessionNameDivisor	= 100000;
   private static final int   pingPeriod			= 9000;
   private static final String  empty	= "...";

   MasterUI		myUI;

   MEventQueue	myEvents;

   HTTPPush		slaves[];
   MCommandQueue  slaveQueues[];
   int		slaveStates[];
   int		runStatus[];

   String		sessionNameTag;

   SessionLog	log;
   long		lastPing;

   int		state	= stateNEW;

   MEvent		currentEvent;

   public Controller(MasterUI ui) {
      myUI = ui;
	this.initialize();
   }

   protected void initialize() {

	int   index;

	myEvents 	  = new MEventQueue();
	myUI.register(myEvents);

	slaves      = new HTTPPush[MasterUI.numberOfSlaves];
	slaveQueues = new MCommandQueue[MasterUI.numberOfSlaves];
	slaveStates = new int[MasterUI.numberOfSlaves];
	runStatus   = new int[MasterUI.numberOfSlaves];

	for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	   log = new SessionLog();

	   slaves[index] 	      = new HTTPPush();
	   slaveQueues[index]   = new MCommandQueue();

	   slaveStates[index]	= slaveDOWN;

	   slaves[index].init(slaveQueues[index], log, myEvents, index);
	   slaves[index].start();	
	}
   }

   public void run() {

	MEvent 	tempEvent;
      int         index;
	MCommand	aCommand;

	myUI.putScreenLog("Controller UP!");
	setNew();

	while(true) { 

	   // Check for events
	   if (myEvents.has() == true) {

		currentEvent = myEvents.get();
		switch(currentEvent.event) {

		   case MEvent.eventUI_STOP:
			handleStop();
			break;

		   case MEvent.eventUI_START:
			handleStart();
			break;

		   case MEvent.eventUI_REFRESH:
			handleRefresh();
			break;

		   case MEvent.eventUI_PAUSE:
			handlePause();
			break;

		   case MEvent.eventSLAVE_RESPONSE:
			handleSlave();
			break;

		   case MEvent.eventTIME:
			// nothin now
			break;
		
		   case MEvent.eventNULL:
		   default:
			System.out.println("Software Detected Fault: Controller.run : Unknown event.");
			break;
		}

	   } else {

		// any run stuff?
		if (state == stateRUNNING) {

		   // Do run stuff.  idle breifly at the end.
		   if (checkDone()) {
			
			myUI.putScreenLog("!DONE!  All slaves report done.");
			tempEvent = new MEvent();
			tempEvent.event = MEvent.eventUI_STOP;
			myEvents.put(tempEvent);
		   }

		   if ( log.getRaw() > (lastPing + pingPeriod)) {

	  		 for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	     		    if (runStatus[index] == runRUNNING) {
	 	   		aCommand = new MCommand();
		   		aCommand.command = MCommand.cmdPING;
	 	   		aCommand.item    = empty;
		   		slaveQueues[index].put(aCommand);
			    }
	  		 }
			lastPing = log.getRaw();
		   }
		   
		   try { this.sleep(idlePeriod); } catch (Exception e) { }

		} else {
		
	 	   // Nuthin' there.  sleep
		   try { this.sleep(sleepPeriod); } catch (Exception e) { }
		}
	   } 

	} // end while

   }

   private void handleStop() { 

	MCommand  aCommand;

	if (state == stateRUNNING) {
	   
	   state = statePENDING;

	   // Create a session name
	   // send a GO to everyone that is enabled
	   int index;
	   for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	      if (myUI.isEnabled(index) == true) {
	 	   aCommand = new MCommand();
		   aCommand.command = MCommand.cmdSTOP;
	 	   aCommand.item    = empty;
		   slaveQueues[index].put(aCommand);
		}

	      if (myUI.isEnabled(index) == true) {
	 	   aCommand = new MCommand();
		   aCommand.command = MCommand.cmdGETLOG;
	 	   aCommand.item    = empty;
		   slaveQueues[index].put(aCommand);
		}

	   }

	   myUI.putScreenLog("STOP.  Session name = " + sessionNameTag);
   
	} else {
	   myUI.putScreenLog("STOP Rejected.  Not running.");
	}

   }
   
   private void handleStart() {

	MCommand	aCommand;

	// only consider if ready or stopped
	checkReady();
	if ((state == stateREADY)||(state == stateSTOPPED)) {
	   
	   // Create a session name
	   long name = log.getRaw() / sessionNameDivisor;
	   log.startTiming();
	   sessionNameTag = Long.toString(name);
	   
	   // send a GO to everyone that is enabled
	   int index;
	   for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	      if (myUI.isEnabled(index) == true) {
		   runStatus[index] = runNOT;

	 	   aCommand = new MCommand();
		   aCommand.command = MCommand.cmdGO;
	 	   aCommand.item    = empty;
		   slaveQueues[index].put(aCommand);
		}
	   }

	   myUI.putScreenLog("START.  Session name = " + sessionNameTag);
   
	} else {
	   myUI.putScreenLog("START Rejected.  Not ready.");
	}
   }
   
   private void handleRefresh() {

	int	  	index;
	MCommand    aCommand;

	// If we are running, reject this.
	if ((state == stateRUNNING)||(state == statePAUSED)) {
	   myUI.putScreenLog("REJECTED REFRESH.  Cannot refresh while running.");
	   return;
	}	

	// Ok.  Queue up targets and loads for all the enabled slaves.
	for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	   runStatus[index] = runNOT;

	   if (myUI.isEnabled(index) == true) {

		aCommand = new MCommand();
		aCommand.command = MCommand.cmdSET_SLAVE_TARGET;
		aCommand.item    = myUI.getTarget(index);
		slaveQueues[index].put(aCommand);

		// Stop it just in case.
		aCommand 	     = new MCommand();
		aCommand.command = MCommand.cmdSTOP;
		aCommand.item    = myUI.getTarget(index);  //  just so it isnt empty
		slaveQueues[index].put(aCommand);

		aCommand 	     = new MCommand();
		aCommand.command = MCommand.cmdTARGET;
		aCommand.item    = myUI.getTestMachine();
		slaveQueues[index].put(aCommand);

		// Read the script.  if it blows up, forget the whole thing
		try { 
		   FileReader in = new FileReader(myUI.getScript(index)); 
		   int 		n;
		   int		size;
		   char[] 		b = new char[1024];
		   StringBuffer	buffer = new StringBuffer();
		   while (true) {
			size     = in.read(b, 0, 1024); 
			if (size == -1) break; 
			buffer.append(b, 0, size); 
		   }
		   in.close();

		   aCommand 	  = new MCommand();
		   aCommand.command = MCommand.cmdNEW;
		   aCommand.value   = myUI.getUser(index);
	 	   aCommand.item    = buffer.toString();
		   slaveQueues[index].put(aCommand);

		   setSlaveDown(index);
  
		} catch (FileNotFoundException e) {  
			myUI.putScreenLog("ERROR! Script file not found for slave " + index);
			setSlaveBad(index);
		} catch (IOException e) { 
			myUI.putScreenLog("ERROR! Error reading script file for slave " + index);
			setSlaveBad(index);
		}
		
	   } else {
		slaveStates[index] = slaveDISABLED;

	   } // end if

	} // end while   

	// Ok.  now we have to wait.
	setNew();
   }

   private void handlePause() {

   } 
   
   private void handleSlave() {

	int 	id = currentEvent.id;

	// Ignore any responses on a bad slave
	if (slaveStates[id] == slaveBAD) return;

	// Act according to the original command
	switch(currentEvent.index) {

	   case MCommand.cmdNEW:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   if (slaveStates[id] == slaveTARGETED) {
			setSlaveUp(id);
			checkReady();		
		   } else if (slaveStates[id] == slaveDOWN) {
			slaveStates[id] = slaveLOADED;
		   } else {
			System.out.println("Software Detected Fault: Controller.handleSlave : Odd state for slave when processing NEW response.");
		   }

		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Couldn't load script on slave " + id);
		   myUI.putScreenLog("---> " + (String)currentEvent.item);
		   setSlaveBad(id);		
		}
		break;

	   case MCommand.cmdTARGET:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   if (slaveStates[id] == slaveLOADED) {
			setSlaveUp(id);
			checkReady();		
		   } else if (slaveStates[id] == slaveDOWN) {
			slaveStates[id] = slaveTARGETED;
		   } else {
			System.out.println("Software Detected Fault: Controller.handleSlave : Odd state for slave when processing TARGET response.");
		   }

		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Couldn't set target on slave " + id);
		   myUI.putScreenLog("---> " + (String)currentEvent.item);
		   setSlaveBad(id);		
		}
		break;

	   case MCommand.cmdSET_SLAVE_TARGET:
		// dont do a thing
		break;

	   case MCommand.cmdSTOP:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   myUI.putScreenLog("STOP.  Slave " + id);
		} else {
		   // Only get pissy if it running
		   if ((state == stateRUNNING)||(state == statePAUSED)) {
		      myUI.putScreenLog("!SLAVE ERROR!  Cound not STOP slave " +id);
		      myUI.putScreenLog("---> " + (String)currentEvent.item);
		      setSlaveBad(id);
		   }		
		}
		break;

	   case MCommand.cmdPAUSE:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   myUI.putScreenLog("PAUSED.  Slave " + id);
		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Cound not PAUSE slave " +id);
		   myUI.putScreenLog("---> " + (String)currentEvent.item);
		   setSlaveBad(id);		
		}
		break;

	   case MCommand.cmdGETLOG:
		if (currentEvent.value == MEvent.rSUCCESS) {
		
		   // Write the logfile
		   try { 
		      FileWriter out = new FileWriter("log-" + sessionNameTag + "-" + currentEvent.id + ".txt"); 
			String     ts  = (String)currentEvent.item;
			out.write(ts, 0, ts.length());
			out.close();
		   } catch (Exception e) {  
		      myUI.putScreenLog("GETLOG - Error writting logfile for " + "log-" + sessionNameTag + "-" + currentEvent.id + ".txt");
		   }
		   myUI.putScreenLog("GETLOG Successful.  Slave " + id);
	  	   runStatus[id] = runREPORTED;

		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Cound not GO slave " +id);
		   myUI.putScreenLog("---> " + (String)currentEvent.item);
		   setSlaveBad(id);		
		}
		checkReported();
		break;

	   case MCommand.cmdGO:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   myUI.putScreenLog("GO Successful.  Slave " + id);
	  	   runStatus[id] = runRUNNING;
		   myUI.setSlaveStatus("RUN", MasterUI.statusGreen, id);
		   checkRunning();
		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Cound not GO slave " +id);
		   myUI.putScreenLog("---> " + (String)currentEvent.item);
		   setSlaveBad(id);		
		}
		break;

	   case MCommand.cmdPING:
		if (currentEvent.value == MEvent.rSUCCESS) {
		   // SNARF the status
		   String   text = (String)currentEvent.item;
		   int pingStatus  = text.indexOf('4');  // is it done?
		   if (pingStatus != -1) {
		      runStatus[id] = runDONE;
		   }
			
		} else {
		   myUI.putScreenLog("!SLAVE ERROR!  Failed PING for " +id);
		   setSlaveBad(id);		
		}
		break;

	   case MCommand.cmdNULL:
	   default:
		System.out.println("Software Detected Fault: Controller.handleSlave : Unknown command.");
		break;
	} // end case

   }

   private void setNew() {
	state = stateNEW;
	myUI.setStatus("NEW", MasterUI.statusGray); 
   }

   private void setReady() {
	state = stateREADY;
	myUI.setStatus("READY!", MasterUI.statusGreen); 
   }

   private void setRunning() {
	state = stateRUNNING;
	myUI.setStatus("RUNNING!", MasterUI.statusGreen);
	lastPing = log.getRaw();
   }

   private void setStopped() {
	state = stateSTOPPED;
	myUI.setStatus("STOPPED", MasterUI.statusGray); 
   }

   private void setSlaveDown(int  slot) {
	slaveStates[slot]  = slaveDOWN;
	myUI.setSlaveStatus("DOWN", MasterUI.statusOrange, slot);
   }

   private void setSlaveBad(int  slot) {
	slaveStates[slot]  = slaveBAD;
	myUI.setSlaveStatus("BAD", MasterUI.statusRed, slot);
   }

   private void setSlaveUp(int  slot) {
	slaveStates[slot] = slaveUP;
	myUI.setSlaveStatus("UP", MasterUI.statusGreen, slot);
   }

   private void checkReady() {

	boolean ready = true;
	int	  index;

	for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	   if ((slaveStates[index] != slaveUP)&&
		 (slaveStates[index] != slaveDISABLED)) ready = false;
	}
	if (ready == true) setReady();
   }

   private boolean checkDone() {

	boolean done = true;
	int	  index;

	for (index = 0; index < MasterUI.numberOfSlaves; index++) {
	   if (runStatus[index] == runRUNNING) done = false;
	}
	return done;
   }

   private void checkRunning() {

	boolean run = true;
	int	  index;

	for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	   if ((runStatus[index] !=  runRUNNING)&&
		 (slaveStates[index] != slaveDISABLED)&&
		 (slaveStates[index] != slaveBAD)) run = false;
	}
	if (run == true) setRunning();
   }

   private void checkReported() {

	boolean report = true;
	int	  index;

	for (index = 0; index < MasterUI.numberOfSlaves; index++) {

	   if ((runStatus[index] !=  runREPORTED)&&
		 (slaveStates[index] != slaveDISABLED)&&
		 (slaveStates[index] != slaveBAD)) report = false;
	}
	if (report == true) setStopped();
   }


}